home *** CD-ROM | disk | FTP | other *** search
/ Collection of Tools & Utilities / Collection of Tools and Utilities.iso / basic / qbnws204.zip / QBNWS204.NWS < prev    next >
Text File  |  1991-12-22  |  103KB  |  2,150 lines

  1.      Volume  2, Number  4                                 December 22, 1991
  2.  
  3.      
  4.      
  5.      
  6.      
  7.      
  8.      
  9.      
  10.      
  11.      
  12.      
  13.      
  14.      
  15.                    **************************************************
  16.                    *                                                *
  17.                    *                    QBNews                      *
  18.                    *                                                *
  19.                    *      International QuickBASIC Electronic       *
  20.                    *                  Newsleter                     *
  21.                    *                                                *
  22.                    *    Dedicated to promoting QuickBASIC around    *
  23.                    *                  the world                     *
  24.                    *                                                *
  25.                    **************************************************
  26.      
  27.      
  28.      
  29.      
  30.      
  31.      
  32.      
  33.      
  34.      
  35.      
  36.      
  37.      
  38.      
  39.      
  40.      
  41.      
  42.         The  QBNews  is  an electronic newsletter  published  by  Clearware
  43.      Computing. It can be freely distributed providing NO CHARGE is charged
  44.      for  distribution.  The  QBNews is copyrighted in  full  by  Clearware
  45.      Computing.  The  authors  hold  the  copyright  to  their   individual
  46.      articles.  All program code appearing in QBNews is released  into  the
  47.      public  domain.   You  may  do what you  wish  with  the  code  except
  48.      copyright  it.  QBNews  must  be  distributed  whole  and  unmodified.
  49.      
  50.      You can write The QBNews at:
  51.      
  52.           The QBNews
  53.           P.O. Box 507
  54.           Sandy Hook, CT 06482
  55.      
  56.      Copyright (c) 1991 by Clearware Computing.
  57.      
  58.      The QBNews                                                   Page    i
  59.      Volume  2, Number  4                                 December 22, 1991
  60.  
  61.      
  62.  
  63.      ----------------------------------------------------------------------
  64.  
  65.                         T A B L E   O F   C O N T E N T S
  66.  
  67.      
  68.      1.  From the Editor's Desk
  69.           The QBNews Turns Three! ......................................  1
  70.           Receiving The QBNews .........................................  2
  71.           Submitting Articles to The QBNews ............................  4
  72.  
  73.      2.  Advertisement
  74.           GFA-BASIC for Windows and DOS ................................  5
  75.  
  76.      3.  The QBNews Professional Library
  77.           Popup Windows by Christy Gemmel ..............................  6
  78.  
  79.      4.  Beginner's Corner
  80.           Using COM 3 and COM 4 with QB by Dick Dennison ............... 31
  81.           The UEVENT Bug by Ray Crumrine ............................... 33
  82.  
  83.      5.  Fun and Games
  84.           Having a Ball by Charles Graham and David Cleary ............. 35
  85.  
  86.  
  87.  
  88.  
  89.  
  90.  
  91.  
  92.  
  93.  
  94.  
  95.  
  96.  
  97.  
  98.  
  99.  
  100.  
  101.  
  102.  
  103.  
  104.  
  105.  
  106.  
  107.  
  108.  
  109.  
  110.  
  111.  
  112.  
  113.  
  114.  
  115.  
  116.      The QBNews                                                   Page   ii
  117.      Volume  2, Number  4                                 December 22, 1991
  118.  
  119.  
  120.  
  121.      ----------------------------------------------------------------------
  122.                    F r o m   t h e   E d i t o r ' s   D e s k
  123.      ----------------------------------------------------------------------
  124.  
  125.      The QBNews Turns Three!
  126.      
  127.      This issue marks the end of the second year for The QBNews. For the
  128.      most part, I am extremely satisfied with how Volume 2 has turned out.
  129.      I have some big plans for Volume 3, but I also have some concerns as
  130.      to the future of The QBNews.
  131.      
  132.      First, we'll discuss Volume 3. For Volume 3, I plan to have each issue
  133.      focus primarily on one topic. This is something I have been wanting to
  134.      do, but when you rely on contributions of articles, you take what you
  135.      can get. Here are the topics I would like to cover in the upcoming
  136.      year, along with their release dates.
  137.      
  138.      Volume 3 Number 1 - March 15, 1992
  139.      
  140.      BASIC 7.x PDS
  141.         New keywords and improvements over QB 4.5
  142.         Using ISAM
  143.         Using Codeview and The Programmers Workbench
  144.         Converting ASM routines over to using Farstrings
  145.      
  146.      Volume 3 Number 2 - June 14, 1992
  147.      
  148.      Graphics Programming
  149.         Using BASIC's graphics routines
  150.         Saving and loading PCX files
  151.         Using Sprites
  152.         Using Fonts
  153.      
  154.      Volume 3 Number 3 - September 13, 1992
  155.      
  156.      Database
  157.         Topics to be determined
  158.      
  159.      Volume 3 Number 4 - November 30, 1992
  160.      
  161.      Communications
  162.         Topics to be determined
  163.      
  164.      As you can see, 1992 will be a full year for The QBNews. However, that
  165.      brings us to my concerns. The level of interest in The QBNews has
  166.      seemed to drop off dramatically. I had to end the Ask The Doctor
  167.      section simply because I didn't receive one question. The number of
  168.      comments and suggestions I receive in the mail is almost nil. And I am
  169.      starting to run low on volunteers to write articles. It seems that at
  170.      the end of each year, I say how I may be discontinuing The QBNews. If
  171.      this disturbing trend keeps up, I will have no choice but to do just
  172.      that. Let's make 1992 a great year for The QBNews.
  173.         
  174.      
  175.      The QBNews                                                     Page  1
  176.      Volume  2, Number  4                                 December 22, 1991
  177.  
  178.      Receiving The QBNews
  179.      
  180.           The  QBNews is distributed mainly through BBS systems around  the
  181.      world.  Some  of  the networks it gets  distributed  through  are  SDS
  182.      (Software   Distribution   System) and  PDN  (Programmers Distribution
  183.      Network). Ask the  sysop of your  local  board about these networks to
  184.      see if there is a  node  in your area.
  185.      
  186.           The  QBNews  can  also  be found  on  CompuServe  in  the  MSLang
  187.      (Microsoft  Language)  forum. It can be found in file area 1 or  2  of
  188.      that  forum. Just search for the keyword QBNEWS. The QBNews will  also
  189.      be available on PC-Link. I send them to Steve Craver, who is the BASIC
  190.      Programming  Forum Host on PC-LINK and he will make them available.  I
  191.      would appreciate anybody who could upload The QBNews to other services
  192.      such as GENIE since I don't have access to these.
  193.      
  194.           I  have also set up a high speed distribution network for  people
  195.      who  would  like to download The QBNews at 9600  baud.  The  following
  196.      boards allow first time callers download privileges also. They are:
  197.      
  198.          Name           Sysop       Location       Number         Node #
  199.      ---------------------------------------------------------------------
  200.      
  201.      Treasure Island  Don Dawson    Danbury, CT    203-791-8532   1:141/730
  202.      
  203.      Gulf Coast BBS   Jim Brewer New PortRichey,FL 904-563-2547   1:365/12
  204.      
  205.      221B Baker St.   James Young   Panama City,FL 904-871-6536   1:3608/1
  206.      
  207.      EMC/80           Jim Harre     St. Louis, MO  314-843-0001   1:100/555
  208.      
  209.      Apple Capitol BBS Bob Finley   Wenatchee, WA  509-663-3618   1:344/61
  210.      
  211.      
  212.           Finally, you can download The QBNews from these vendors BBS's:
  213.      
  214.      The Crescent Software Support BBS   203-426-5958
  215.      
  216.      The EllTech Support BBS             404-928-7111
  217.      
  218.      The Microhelp BUG BBS               404-552-0567
  219.                                          404-594-9625
  220.      
  221.      
  222.      You do not have to be a customer of these vendors in order to download
  223.      The  QBNews, but the Microhelp BBS only allows non-members 15  minutes
  224.      of time per call.
  225.      
  226.         If you would like to receive The QBNews on disk, I offer a yearly
  227.      subscription for $20.00. This includes four disks containing each
  228.      issue as it is published. If you would like a disk with all the back
  229.      issues of The QBNews, please enclose an additional $5.00. The pricing
  230.      structure is as follows:
  231.      
  232.      
  233.      The QBNews                                                     Page  2
  234.      Volume  2, Number  4                                 December 22, 1991
  235.  
  236.      Base Price for 1 Year -       $20.00
  237.      Disk with Back Issues -        $5.00
  238.      3.5" disk surcharge -          $5.00
  239.      Canada and Mexico surcharge -  $5.00
  240.      All other foreign orders -    $10.00
  241.      
  242.           The  base price includes 5.25" 360k disks. Send a check or  money
  243.      order in U.S. funds to:
  244.      
  245.           The QBNews
  246.           P.O. Box 507
  247.           Sandy Hook, CT 06482
  248.      
  249.           Please  be  sure  to specify what archive format  you  want.  The
  250.      QBNews normally uses PKZip as it's archiver.
  251.      
  252.  
  253.  
  254.  
  255.  
  256.  
  257.  
  258.  
  259.  
  260.  
  261.  
  262.  
  263.  
  264.  
  265.  
  266.  
  267.  
  268.  
  269.  
  270.  
  271.  
  272.  
  273.  
  274.  
  275.  
  276.  
  277.  
  278.  
  279.  
  280.  
  281.  
  282.  
  283.  
  284.  
  285.  
  286.  
  287.  
  288.  
  289.  
  290.      The QBNews                                                     Page  3
  291.      Volume  2, Number  4                                 December 22, 1991
  292.  
  293.      Submitting Articles to The QBNews
  294.      
  295.           The QBNews relies on it's readers to submit articles. If you  are
  296.      interested in submitting an article, please send a disk of Ascii  text
  297.      of no more than 70 characters per line to:
  298.      
  299.           The QBNews
  300.           P.O. Box 507
  301.           Sandy Hook, CT 06482
  302.      
  303.      Articles can also be submitted via E-Mail. Send them via Compuserve to
  304.      76510,1725 or via FidoNet to 1:141/777. I can be reached at the  above
  305.      addresses as well as on Prodigy as HSRW18A.
  306.      
  307.  
  308.  
  309.  
  310.  
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324.  
  325.  
  326.  
  327.  
  328.  
  329.  
  330.  
  331.  
  332.  
  333.  
  334.  
  335.  
  336.  
  337.  
  338.  
  339.  
  340.  
  341.  
  342.  
  343.  
  344.  
  345.  
  346.  
  347.      The QBNews                                                     Page  4
  348.      Volume  2, Number  4                                 December 22, 1991
  349.  
  350.  
  351.  
  352.      ----------------------------------------------------------------------
  353.                             A d v e r t i s e m e n t
  354.      ----------------------------------------------------------------------
  355.  
  356.      GFA-BASIC GIVES YOU MORE PROGRAMMING POWER             Now includes a
  357.         than both Visual Basic and Realizer                  dBASE III/IV
  358.                     COMBINED!                                   engine!
  359.      
  360.      If your going to program for Windows, GFA-BASIC is the one tool that
  361.      can help you migrate your Qwick BASIC programs over. It's easy to use, 
  362.      contains hundreds of commands and functions and is lightening fast.  
  363.      After buying many libraries for Visual Basic and Realizer, one key 
  364.      customer gave up and with GFA, implemented a networked Windows 
  365.      application on 100 PC's in record time.  The following comparison was 
  366.      derived from that project:                    GFA     Visual  Realizer
  367.                                                   BASIC    Basic
  368.      Visual programming tools                      YES      YES     YES
  369.      Dynamic Modification of Window Spec           YES      NO      YES
  370.      Capability to Run Multiple Parent Windows     YES      YES     NO
  371.      Resolution Independent Dialog Boxes           YES      NO      YES
  372.      Pre-defined File-Select Boxes                 YES      NO      YES
  373.      All Direct Windows API calls w/out defining   YES      NO      NO
  374.      Built-In dBASE III/IV read/update/append      YES      NO      NO 
  375.      Built-In record locking for multi-user/LAN    YES      NO      NO DLL 
  376.      Easy internal multi-tasking                   YES      NO      YES
  377.      Advanced Graphics e.g., spline, stretch       YES      NO      NO
  378.         bezier curve. ellipse, arcs                          
  379.      Dynamic BITMAPS--stretch, re-size             YES      NO      NO
  380.      Binary File load/save                         YES      NO      YES
  381.      Total # Commands & Functions                  >700     <250    <300
  382.      Maximum array size                            20MEG    64K     64K
  383.      Full set array Matrix commands                YES      NO      NO
  384.      Multiple Dimension Arrays                     YES      YES     NO
  385.      User defined structures i.e., TYPES           YES      YES     NO
  386.      Integer & floating point math                 YES      YES     FP ONLY
  387.      Advanced math functions(trig & statistics)    YES      NO      SOME
  388.      Direct fast COM port access commands          YES      NO      LIB 
  389.      DLL size required with compiled .exe          150K     250K    400K 
  390.      Typical Program Execution Speed               FAST     SLOW    SLOW 
  391.      Mouse/GUI applications portable to MS-DOS     YES      NO      NO 
  392.      Add'l libraries you need to buy               NONE     MANY    SOME
  393.      
  394.                           Half Price Introductory Offer
  395.                                              List        Intro
  396.           GFA-BASIC for Windows 3.0          $495        $295
  397.           GFA-BASIC for MS-DOS               $295        $195
  398.           Both Windows and DOS version       $790        $395*
  399.          *Includes newly released dBASE III/IV Engine for Dos & Windows
  400.      Call us with your order and we will GUARANTEE that you will be 
  401.      satisfied with these products for one full year!
  402.      
  403.      Call 1-800-766-6GFA               GFA Software Technologies, Inc. 
  404.                                        27 Congress St., Salem, MA 01970 
  405.      
  406.      The QBNews                                                     Page  5
  407.      Volume  2, Number  4                                 December 22, 1991
  408.  
  409.  
  410.  
  411.      ----------------------------------------------------------------------
  412.           T h e   Q B N e w s   P r o f e s s i o n a l   L i b r a r y
  413.      ----------------------------------------------------------------------
  414.  
  415.      Popup Windows by Christy Gemmel
  416.      
  417.      By now, you should have a good idea of how assembly-language can be
  418.      used with your QuickBASIC programs.  The routines we've developed for
  419.      our library, so far, are short and simple, but they've already given
  420.      you a lot more power over your mouse and the video display.  Hopefully
  421.      your appetite is whetted for more.
  422.      
  423.      Well now we're starting on the heavy stuff.  What I have for you next
  424.      is a complete Window Management System.  Windows can be popped up at
  425.      any location on the screen, overlayering each other if you require.
  426.      They can be displayed in any combination of colours or attributes and
  427.      come in a variety of border styles.  They can have shadow, to give a
  428.      three-dimensional effect, and you can zoom them onto the screen for a
  429.      really slick effect...
  430.      
  431.      What are Windows?  If you've used the QuickBASIC environment at all,
  432.      then you've used Windows.  When you press <ALT><F> to bring down the
  433.      File Menu, the list of options presented there is in a window. Notice
  434.      how any characters which were hidden when the menu appeared, are
  435.      restored, intact, after you've made your choice and the menu window
  436.      vanishes.  Windows are areas of the screen which are used to hold
  437.      transient data and messages to the user.  They make the most of the
  438.      limited display space available and remove the need to be constantly
  439.      redrawing the screen each time your program communicates with the
  440.      outside world.  Properly presented, windows can give the illusion of
  441.      multi-tasking, even on a single-processor machine like your PC.
  442.      
  443.      Nowadays, no program worth its' salt can be without a window of some
  444.      kind.  If YOUR program is going to stand out amongst all the others,
  445.      however, they've got to be done professionally.  Your windows must
  446.      appear instantly and vanish, just as quickly, when no longer needed.
  447.      They must be as large or as small as is necessary, for the data which
  448.      you need to display, and you should have a plentiful supply, enough
  449.      for all the possible circumstances that your program might encounter.
  450.      High-level languages, unfortunately, are just not fast enough to meet
  451.      all these requirements.
  452.      
  453.      Looks like it'll have to be assembler again ...
  454.      
  455.      I'll start by explaining how windows work.  You've already seen with
  456.      FASTPRINT, how  large  amounts of text can be output directly to the
  457.      video display in a flash. That bit shouldn't be any bother to us now.
  458.      For the rest, the main problem seems to be how to restore the
  459.      original screen contents when we take the window away, especially if
  460.      we have windows overlapping on the same portion of the screen.
  461.      
  462.      The way to do it is this. First we establish a buffer in memory,
  463.      large enough to hold the total contents of all the windows we plan to
  464.      have on the screen at any one time.  Then, just before we pop up a
  465.      
  466.      The QBNews                                                     Page  6
  467.      Volume  2, Number  4                                 December 22, 1991
  468.  
  469.      window, we must copy the contents of the screen rectangle which will
  470.      be covered by that window, into the buffer we have reserved.  This
  471.      way, when we have have finished with the window, all we have to do is
  472.      to copy the original data from the buffer, back into the original
  473.      rectangle it came from.
  474.      
  475.      Sounds easy?  Well it is.  The trick is in keeping track of which lot
  476.      of buffer data corresponds to which window on the screen and where to
  477.      put it all when we put it back. The rest is just byte shifting.
  478.      
  479.      How big does the buffer need to be?   It depends on the both the
  480.      number and the size of the windows which we plan to use.  Remember
  481.      that each character displayed on the screen takes up two bytes of
  482.      information, one for the ASCII character code and the other for its'
  483.      associated attribute.  So a window 20 columns wide by 12 rows high
  484.      would need (20 x 12) * 2  =  480 bytes of storage to  hold the
  485.      information under it.  Our window driver will contain 16K of internal
  486.      storage, the equivalent of 4 full screens, less a small amount of
  487.      overhead, enough for most reasonable applications.
  488.      
  489.      To make things really easy, we'll operate the buffer on the stack
  490.      principle, so that the most recently displayed window is always the
  491.      first one to be removed.  This is known as the LIFO method (Last In,
  492.      First Out), and it prevents the possibility of gaps appearing in our
  493.      buffer as windows are removed in different sequence from the one in
  494.      which they were created.  You wouldn't believe the memory management
  495.      problems THAT can cause!
  496.      
  497.      What calling conventions should we use?   We'll need to define the
  498.      screen rectangle which the window will occupy, also the display
  499.      attribute, since we want to pop up different coloured windows for
  500.      different types of messages.  How about border style?  In the PANEL
  501.      subprogram we used two types of border, single and double lines,
  502.      perhaps we should offer a wider choice this time, here are some
  503.      possible permutations.
  504.      
  505.              ┌────┐
  506.              │ 1. │    Single-lined box all round the window
  507.              └────┘
  508.              ╔════╗
  509.              ║ 2. ║    Double-lined box all round the window
  510.              ╚════╝
  511.              ╒════╕
  512.              │ 3. │    Single vertical, double horizontal
  513.              ╘════╛
  514.              ╓────╖
  515.              ║ 4. ║    Single horizontal, double vertical
  516.              ╙────╜
  517.      
  518.      An argument of zero can be used for plain windows, without borders.
  519.      They might be useful sometimes.
  520.      
  521.      Is that the lot?  Wait a minute though, a common application of
  522.      windows is for Pull Down Menus, like the QuickBASIC File Menu which
  523.      
  524.      The QBNews                                                     Page  7
  525.      Volume  2, Number  4                                 December 22, 1991
  526.  
  527.      you get by pressing <ALT><F>.  If we're going to cater for this sort
  528.      of thing we need to include border styles which merge into a menu bar
  529.      at the top, these for instance;
  530.      
  531.              ══╤═══════════════╤══         ══╦═══════════════╦══
  532.                │               │             ║               ║
  533.                │               │             ║               ║
  534.                │      5.       │             ║      6.       ║
  535.                │               │             ║               ║
  536.                │               │             ║               ║
  537.                ╘═══════════════╛             ╚═══════════════╝
  538.              ──┬───────────────┬──         ──╥───────────────╥──
  539.                │               │             ║               ║
  540.                │               │             ║               ║
  541.                │      7.       │             ║       8.      ║
  542.                │               │             ║               ║
  543.                │               │             ║               ║
  544.                └───────────────┘             ╙───────────────╜
  545.      
  546.      The SHADOW switch (Parameter 7), will be used to add a black shadow
  547.      underneath the window, Giving it a three dimensional effect.  Setting
  548.      P7 to 1, puts the shadow on the left-hand side.  Setting P7 to 2 puts
  549.      it on the right. Any other value prevents shadow.
  550.      
  551.                ┌───────────────┐             ╓───────────────╖
  552.               █│               │             ║               ║█
  553.               █│               │             ║               ║█
  554.               █│  Left Shadow  │             ║ Right Shadow  ║█
  555.               █│               │             ║               ║█
  556.               █│               │             ║               ║█
  557.               █└───────────────┘             ╙───────────────╜█
  558.               ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀               ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
  559.      
  560.      Setting Parameter 8 to a non-zero value will cause the window to ZOOM
  561.      onto the screen. What this means is that, starting at a point source,
  562.      successively larger versions of the window  will be drawn until it is
  563.      the size required.  The process is extremely fast and impressive, and
  564.      will add a very professional touch to your programs.
  565.      
  566.      Like all assembly-language routines linked to QuickBASIC  programs,
  567.      our window code must use the Medium memory model. We will also be
  568.      calling a number of external routines which are listed here.  Some of
  569.      these you will recognise from the FASTPRINT article.  Source code for
  570.      the others is provided with this issue.
  571.      
  572.                      .model medium
  573.      
  574.                      extrn   Delay:proc
  575.                      extrn   Explode:proc
  576.                      extrn   ScreenAddress:proc
  577.                      extrn   ScreenCopy:proc
  578.                      extrn   ScreenWrite:proc
  579.                      extrn   VideoType:proc 
  580.                      extrn   WriteByte:proc
  581.      
  582.      The QBNews                                                     Page  8
  583.      Volume  2, Number  4                                 December 22, 1991
  584.  
  585.      
  586.      The program will consist of two seperate modules, one is to draw the
  587.      window on the screen and the other to remove it when it is no longer
  588.      required.  Both routines will be called from QuickBASIC, so they must
  589.      be declared PUBLIC.
  590.      
  591.                      public  PopUp, ShutUp
  592.      
  593.                      .code
  594.      
  595.      This program uses quite a lot of internal data.  Let's declare it now
  596.      and get it over with.  For now, notice the buffer used for holding
  597.      screen data, I have set this to 16KB (4000 Hex bytes) to give us room
  598.      for lots of windows. If you find that this is too big (or too small),
  599.      set it to your own value. No other changes are necessary.
  600.      
  601.      Ulc             label   word                    ; Upper left co-ordinate
  602.      TlRow           db      ?                       ; Top left screen row
  603.      TlCol           db      ?                       ; Top left screen column
  604.      Lrc             label   word                    ; Lower right co-ordinate
  605.      BrCol           db      ?                       ; Right column of window
  606.      BrRow           db      ?                       ; Bottom row of window
  607.      Area            label   word
  608.      Breadth         db      ?                       ; Window width (inc shadow)
  609.      Height          db      ?                       ; Window Height (inc shadow)
  610.      ToDo            label   word
  611.      Cols2do         db      ?                       ; Columns to restore
  612.      Rows2do         db      ?                       ; Rows to restore
  613.      Rows            db      ?                       ; Screen length in rows
  614.      Columns         db      ?                       ; Screen width in columns
  615.      Increment       dw      ?                       ; Interval between rows
  616.      BuffPtr         dw      ?                       ; Pointer to current buffer
  617.      BuffTop         dw      ?                       ; Offset of first buffer row
  618.      BuffEnd         dw      ?                       ; Offset of last buffer row
  619.      WinTop          dw      ?                       ; Offset of first screen row
  620.      WinEnd          dw      ?                       ; Offset of last screen row
  621.      
  622.      TopLeft         label   byte
  623.                      db      ' ┌╔╒╓╤╦┬╥'             ; TL Corner characters
  624.      TopRight        label   byte
  625.                      db      ' ┐╗╕╖╤╦┬╥'             ; TR Corner characters
  626.      BotLeft         label   byte
  627.                      db      ' └╚╘╙╘╚└╙'             ; BL Corner characters
  628.      BotRight        label   byte
  629.                      db      ' ┘╝╛╜╛╝┘╜'             ; BR Corner characters
  630.      Vertical        label   byte
  631.                      db      ' │║│║│║│║'             ; Vertical characters
  632.      Horizontal      label   byte
  633.                      db      ' ─══─══──'             ; Horizontal characters
  634.      
  635.      Buffer          label   byte                    ; Start of screen buffer
  636.                      db      4000h dup(0)
  637.      BufferTop       dw      0                       ; End of screen Buffer
  638.      
  639.      
  640.      The QBNews                                                     Page  9
  641.      Volume  2, Number  4                                 December 22, 1991
  642.  
  643.      Here's our introduction with BP being set, as usual, to point to the
  644.      stack.  Once we've used it to obtain all the parameters, we are going
  645.      to point DS to our own local data.  We need to save its' original
  646.      contents, therefore, so that they can be reset on return.  ES will be
  647.      used to point to video memory so we'd better save that as well,
  648.      likewise the two Index registers.
  649.      
  650.      PopUp           proc    far
  651.                      push    bp                      ; Save Base pointer
  652.                      mov     bp,sp                   ; Establish stack frame
  653.                      push    ds                      ; Preserve segment
  654.                      push    es                      ;    registers and
  655.                      push    di                      ;      index
  656.                      push    si                      ;        pointers
  657.      
  658.      You will remember this call from the FASTPRINT article.  VIDEOTYPE is
  659.      a routine which we wrote to collect important information about the
  660.      kind of video display the host computer has.  In this case we used it
  661.      to determine the number of rows and columns the screen is set to. The
  662.      routine also found the address of the video display buffer and number
  663.      of the video status port and has stored this information, internally,
  664.      in its' own module. We will need it later. 
  665.      
  666.                      call    VideoType               ; Get video parameters
  667.      
  668.      POPUP and SHUTUP, The two routines in this module, do not use string
  669.      data as arguments so we do not need to write a seperate version for
  670.      BASIC 7 using far strings.  All the parameters that we will be  using
  671.      are passed BY VALUE on the stack, so we do not need to keep our DS
  672.      register pointing to QuickBASIC's Data Segment (DGROUP).  Let's point
  673.      it to our own local data so that we can access it more easily.
  674.      
  675.                      push    cs                      ; Align Code and
  676.                      pop     ds                      ;    Data segments
  677.      
  678.      Many QuickBASIC programmers who are new to Assembly-language assume
  679.      that the DS register must be kept pointing to DGROUP while arguments
  680.      are read off the stack. This is only true when it is the address of
  681.      arguments which are passed, by reference, (QuickBASIC's default) and
  682.      the routine must go looking in DGROUP for the variables themselves.
  683.      Values read directly from stock, using the BP register as a pointer,
  684.      are indexed through the SS (Stack Segment) register, so DS can be
  685.      pointing elsewhere.
  686.      
  687.      We will store the data that VIDOTYPE collected, locally, in our own
  688.      code segment.  This saves using any of QuickBASIC's own data space
  689.      which, even if you use BASIC 7 is always at a premium.  Aligning the
  690.      Code and Data registers allows us to store variables here without
  691.      having to use nasty segment override instructions and reduces
  692.      overhead considerably.
  693.      
  694.                      mov     Rows,bl                 ; Store screen height
  695.                      mov     Columns,ah              ; Store screen width
  696.                      mov     al,ah                   ; Transfer number of
  697.      
  698.      The QBNews                                                     Page 10
  699.      Volume  2, Number  4                                 December 22, 1991
  700.  
  701.                      xor     ah,ah                   ;    columns to AX
  702.                      shl     ax,1                    ;      and convert
  703.                      mov     Increment,ax            ;        to bytes
  704.      
  705.      While we have the screen width handy in AH, we may as well take the
  706.      opportunity to calculate the increment between rows for when we start
  707.      drawing vertical lines.  Since there are two bytes per column, one
  708.      each for the character and it's display attribute, we must multiply
  709.      the number of columns by two.  Shifting each bit of the number to the
  710.      left with SHL does this just as effectively and much faster than the
  711.      MUL instruction and saves having to use another register.
  712.      
  713.      We've some chores to do before we can start on the interesting bits,
  714.      Just as we did in FASTPRINT, we must first examine the four arguments
  715.      which define the rectangle over which the window will appear, testing
  716.      them for legal values;
  717.      
  718.                      mov     al,[bp+20]              ; Get top-left row
  719.                      dec     al                      ; Make it base zero
  720.                      cmp     al,0                    ; Check for
  721.                      jge     Pop_01                  ;    legal
  722.                      xor     al,al                   ;      values
  723.      Pop_01:
  724.                      mov     TlRow,al                ; Save top-left row
  725.                      mov     al,[bp+18]              ; Get top-left column
  726.                      dec     al                      ; Make it base zero
  727.                      cmp     al,0                    ; Check for
  728.                      ja      Pop_02                  ;    legal
  729.                      mov     al,1                    ;      values
  730.      Pop_02:
  731.                      mov     TlCol,al                ; Store top-left column
  732.                      mov     al,[bp+16]              ; Get window height
  733.                      cmp     al,2                    ; Check for
  734.                      ja      Pop_03                  ;    legal
  735.                      mov     al,3                    ;      values
  736.      Pop_03:
  737.                      mov     [bp+16],al              ; Store window height
  738.                      mov     al,[bp+14]              ; Get window width
  739.                      cmp     al,2                    ; Check for
  740.                      ja      Pop_04                  ;    legal
  741.                      mov     al,3                    ;      values
  742.      Pop_04:
  743.                      mov     [bp+14],al              ; Store window width
  744.                      mov     al,TlRow                ; Get start row
  745.                      mov     ah,[bp+16]              ; Get number of rows
  746.                      add     al,ah                   ; Add 'em together
  747.                      cmp     al,Rows                 ; Out of bounds?
  748.                      jb      Pop_05                  ; No, carry on
  749.                      jmp     Pop_38                  ; Else abort
  750.      Pop_05:
  751.                      dec     al                      ; Store bottom
  752.                      mov     BrRow,al                ;    row number
  753.                      mov     al,TlCol                ; Get start column
  754.                      mov     ah,[bp+14]              ; Get number of columns
  755.      
  756.      The QBNews                                                     Page 11
  757.      Volume  2, Number  4                                 December 22, 1991
  758.  
  759.                      add     al,ah                   ; Add 'em together
  760.                      cmp     al,Columns              ; Out of bounds?
  761.                      jb      Pop_06                  ; No, carry on
  762.                      jmp     Pop_38                  ; Else abort
  763.      Pop_06:
  764.                      dec     al                      ; Store rightmost
  765.                      mov     BrCol,al                ;    column number
  766.      
  767.      Unlike the first two, the second pair of arguments do not give us the
  768.      coordinates of the lower-right corner directly. To make it easier for
  769.      our users, we'll accept the HEIGHT of the window (in rows) and the
  770.      WIDTH of the window (in columns), instead.  We must make a couple of
  771.      small calculations to ensure that the window fits within the borders
  772.      of the screen, not forgetting to allow for shadow.
  773.      
  774.      There are more parameters to come.  The attribute value doesn't need
  775.      to be checked, since we will only be using the least-significant byte
  776.      of the integer value passed and all possible values (0-255) that this
  777.      can contain are legal.  From  here on, we won't abort if an illegal
  778.      argument is passed to us, instead we'll use a default.  The default
  779.      for BORDER type is 1, a single-lined border.
  780.      
  781.                      mov     al,[bp+10]              ; Get required border type
  782.                      cmp     al,0                    ; Check
  783.                      jb      Pop_07                  ;    for
  784.                      cmp     al,8                    ;      legal
  785.                      ja      Pop_07                  ;        values
  786.                      jmp     short Pop_08
  787.      Pop_07:
  788.                      mov     byte ptr [bp+10],1      ; Set default (single line)
  789.      Pop_08:
  790.                      mov     al,[bp+8]               ; See if shadow is required
  791.                      cmp     al,0                    ; Check
  792.                      jl      Pop_09                  ;    for
  793.                      cmp     al,4                    ;      legal
  794.                      ja      Pop_09                  ;        values
  795.                      jmp     short Pop_10
  796.      Pop_09:
  797.                      mov     byte ptr [bp+8],0       ; Set default (no shadow)
  798.      Pop_10:
  799.                      cmp     word ptr [bp+6],0       ; Check for
  800.                      jge     Pop_11                  ;    legal
  801.                      mov     word ptr [bp+6],20      ;      values
  802.      
  803.      The default for SHADOW is zero - no shadow.  With ZOOM, the argument
  804.      is now used as a delay counter in milliseconds.  Previous versions of
  805.      this routine treated any non-zero value as logical TRUE and generated
  806.      a fixed delay count. To cater for programs which still expect this, I
  807.      have translated any negative ZOOM values (eg -1) into a delay of 20,
  808.      which approximates to the previous ZOOM speed.
  809.      
  810.      This, incidently, is an important point to bear in mind when you are
  811.      writing libraries for commercial (or Shareware) release. Whenever you
  812.      update an existing routine, remember that your current users may have
  813.      
  814.      The QBNews                                                     Page 12
  815.      Volume  2, Number  4                                 December 22, 1991
  816.  
  817.      to convert their old programs before they can use them with your new 
  818.      version.  If you can make this job as painless as possible they will
  819.      bless you for it.
  820.      
  821.      Pop_11:
  822.                      mov     ax,1                    ; Initialise                   
  823.                      push    ax                      ;    millisecond 
  824.                      call    Delay                   ;      delay routine
  825.      
  826.      ZOOM, the speed at which the window will explode onto the screen, is
  827.      controlled by the millisecond delay value we are passed.  The smaller
  828.      this number, the faster the explosion. We need, however to initialise
  829.      the actual delay routine which is in our other module, DISPLAY.ASM.
  830.      Since initialisation, itself, takes a little time it is best to do it
  831.      here, where it won't be noticed, rather than add its' overhead to the
  832.      first appearance of our window.
  833.      
  834.      Some more calculation is necessary, to obtain the co-ordinates of the
  835.      window. If it is going to have shadow then an extra row and column of
  836.      screen data will have to be saved in the buffer.  Better work out the
  837.      area of the window, so we can check if there is enough space left in
  838.      the buffer.  Multiply the number of rows in AX by the number of
  839.      columns in BX, this leaves the product in AX.  Double AX again to
  840.      account for attribute bytes and transfer the result to CX.
  841.      
  842.                      xor     ax,ax                   ; Get number of rows
  843.                      mov     al,[bp+16]              ;    into AX
  844.                      xor     bx,bx                   ; Get number of columns
  845.                      mov     bl,[bp+14]              ;    into BX
  846.                      cmp     byte ptr [bp+8],0       ; Shadow required?
  847.                      jz      Pop_12                  ; No, skip next bit
  848.                      inc     al                      ; Add a row
  849.                      inc     bl                      ;    and a column
  850.      Pop_12:
  851.                      mov     Height,al               ; Store adjusted height
  852.                      mov     Breadth,bl              ; Store adjusted width
  853.                      mul     bl                      ; Find area
  854.                      shl     ax,1                    ;    in bytes
  855.                      mov     cx,ax                   ; Transfer to CX
  856.                      mov     si,offset Buffer        ; Start of screen stack
  857.      
  858.      That big, huh?  Let's see if it will fit.
  859.      
  860.      Perhaps I should explain how the buffer is organised.  Since each
  861.      block of screen data stored in it is likely to be of a different
  862.      size, it is necessary to use the concept of a LINKED LIST.  By this
  863.      method, every block in the buffer begins with a pointer to the next
  864.      block.  In searching for a free block, we follow the chain along the
  865.      list until we find an empty pointer.  Any space following this should
  866.      be available for use.  Note that, initially, the buffer area was set
  867.      to zeroes.
  868.      
  869.      In addition to the next-block pointer, we will store the offset
  870.      address of the top left corner of each window in the buffer.  The
  871.      
  872.      The QBNews                                                     Page 13
  873.      Volume  2, Number  4                                 December 22, 1991
  874.  
  875.      height of the window, in rows, and it's width, in columns (including
  876.      any extra allowed for shadow), will also be stored.  This should be
  877.      enough to specify the screen rectangles we need to save.  Every block
  878.      of buffer storage, therefore, will contain the following data ...
  879.      
  880.                                 width  height
  881.                                    │     │
  882.         ┌───────────┬───────────┬─────┬─────┬──────            ──────┐
  883.         │  2 bytes  │  2 bytes  │  2 bytes  │    Variable length     │
  884.         └───────────┴───────────┴─────┴─────┴──────            ──────┘
  885.               │           │           │                  │
  886.          Pointer to   Offset of  Dimensions   Storage for screen data
  887.          next block   UL Corner  of window
  888.                       of window  (including
  889.                                  any shadow)
  890.      
  891.      The SI register is now pointing to the start of the buffer, so we can
  892.      examine the first two bytes right away.  If they are empty (set to
  893.      zero), then the whole buffer is empty and we can begin storing our
  894.      block of screen data at once. If however, the first two bytes contain
  895.      data, then we can assume that it is a pointer to the beginning of the
  896.      NEXT block, so we load SI with this address and repeat the process.
  897.      Only when SI points to a word of zeroes, do we we have free space to
  898.      store our screen.
  899.      
  900.      Pop_13:
  901.                      cmp     word ptr [si],0         ; Is anything there?
  902.                      jz      Pop_14                  ; No, must be free space
  903.                      mov     si,[si]                 ; Point to next block
  904.                      jmp     Pop_13                  ;    and try again
  905.      Pop_14:
  906.                      mov     ax,si                   ; Point AX to entry
  907.                      add     ax,6                    ; Allow for pointers
  908.                      add     ax,cx                   ;    and area to be saved
  909.                      mov     dx,offset BufferTop     ; Point DX to end of stack
  910.                      cmp     ax,dx                   ; Enough space left?
  911.                      jb      Pop_15                  ; Yes, Carry on
  912.                      jmp     Pop_38                  ; Otherwise abort
  913.      
  914.      Before we begin copying data to the buffer, we must make sure that
  915.      there is enough space left to hold it, for all we know there may be
  916.      other windows up on the screen.  We have already calculated the size
  917.      of the block we need to save.  Add another six bytes for our own
  918.      pointers, then add the result to the offset address of the current
  919.      block.  If the resulting address is past the end of the buffer, then
  920.      there is not enough room for the new window and there is no point in
  921.      proceeding any further.
  922.      
  923.      If there is room for another window, then we can go ahead.  Since AX,
  924.      as a result of the last calculation, is already holding the address
  925.      of the first byte past our save area, we can store this immediately,
  926.      as the next-block-pointer.
  927.      
  928.      Pop_15:
  929.      
  930.      The QBNews                                                     Page 14
  931.      Volume  2, Number  4                                 December 22, 1991
  932.  
  933.                      mov     [si],ax                 ; Set pointer to next block
  934.      
  935.      SCREENADDRESS is one of the support routines we developed for our
  936.      FASTPRINT utility two issues back. We used it to calculate the screen
  937.      address of the position where printing was to start. This time we are
  938.      looking for the address of the top-left corner of the rectangle which
  939.      will be covered by our window.  If the window is going to have shadow
  940.      on the left, we must also save an extra column on the left-hand side.
  941.      
  942.                      mov     ax,Ulc                  ; Get row-column co-ordinates
  943.                      call    ScreenAddress           ; Convert to memory address
  944.                      mov     WinTop,di               ; Save it for later
  945.                      test    byte ptr [bp+8],1       ; Left shadow?
  946.                      jz      Pop_16                  ; No, skip next bit
  947.                      dec     di                      ; One column
  948.                      dec     di                      ;    to the left
  949.      Pop_16:
  950.                      inc     si                      ; Bump buffer
  951.                      inc     si                      ;    pointer
  952.                      mov     [si],di                 ; Store it in buffer
  953.                      inc     si                      ; Bump buffer
  954.                      inc     si                      ;    pointer
  955.                      mov     ax,Area                 ; Get panel Area
  956.                      mov     [si],ax                 ; Store them in the buffer
  957.                      inc     si                      ; Bump pointer to
  958.                      inc     si                      ;    screen storage block
  959.      
  960.      Once we have the address, it is stored, in the save buffer, at the
  961.      word following the next-block-pointer. Finally, the third word in the
  962.      save buffer is loaded  with the height and width of the rectangle
  963.      we are going to save.
  964.      
  965.      It is worth drawing your attention to the method we just used to
  966.      access the dimensions of the rectangle. When we originally stored the
  967.      height and width after they were calculated, we saved them as BYTE
  968.      values.  Now we come to use them we are able to copy them both, as a
  969.      single WORD, into AX with one instruction.  One of the nice features
  970.      of MASM is that it allows us to define the same block of memory in
  971.      several different ways:
  972.      
  973.      Area            label   word
  974.      Breadth         db      ?                       ; Window width (inc shadow)
  975.      Height          db      ?                       ; Window Height (inc shadow)
  976.      
  977.      In this example, BREADTH and HEIGHT are labels which define single
  978.      bytes, but AREA, which also refers to the same location, is of size
  979.      WORD (2 bytes). By using this as a reference, we can load both values
  980.      directly into AX in one move, instead of the two which would be
  981.      necessary if we had to load AH and AL individually.
  982.      
  983.      If you have stuck with me, through the examples in these articles, so
  984.      far, you will have realised that most of the donkey-work of assembly-
  985.      language programming, lies in setting up registers and data ready for
  986.      some operation which, in itself, only takes a few instructions.  Such
  987.      
  988.      The QBNews                                                     Page 15
  989.      Volume  2, Number  4                                 December 22, 1991
  990.  
  991.      is the case here.  We are going to use a nested loop to succesively
  992.      copy each column of each row of the screen rectangle which our window
  993.      will cover, into the memory buffer.
  994.      
  995.      As well as being one of the four 8086 general-purpose registers, CX
  996.      can also be used as a loop counter.  Our outer processing loop will
  997.      govern the number of rows copied.  This value is already in AH. Let's
  998.      begin by copying it to CX.
  999.      
  1000.                      xor     cx,cx                   ; Get number of rows
  1001.                      mov     cl,ah                   ;    in CX
  1002.      
  1003.      Before we start, we must swap over the pointers we have been using so
  1004.      far. Currently the Data Segment register (DS) is pointing to the Code
  1005.      Segment where our local data is.  Now, however, we need to treat the
  1006.      video display buffer as our Data segment because we are going to copy
  1007.      that part of the screen which will lie underneath our window into the
  1008.      buffer for safekeeping. 
  1009.      
  1010.                      xchg    di,si                   ; Swap pointers
  1011.                      push    ds                      ; Point DS to
  1012.                      push    es                      ;    video segment
  1013.                      pop     ds                      ;      and ES to
  1014.                      pop     es                      ;        local data
  1015.      
  1016.      The inner loop controls the number of columns to be copied. We'll use
  1017.      CX as the counter for this as well.  Save the row count on the stack,
  1018.      temporarily, while  we load the column count into CX.  Remember this
  1019.      number includes an extra column for shadow, if specified.  We'll also
  1020.      push SI, which contains the screen starting address, for reasons
  1021.      which will become clear.
  1022.      
  1023.      Pop_17:          
  1024.                      push    cx                      ; Save row count
  1025.                      push    si                      ; Save screen pointer
  1026.                      mov     cl,cs:Breadth           ; Set column count
  1027.      
  1028.      Now we can start moving data. SI is currently pointing to the address
  1029.      in video memory, where the first character to be moved is located. DS
  1030.      has the segment address of video memory.  DI is pointing to the start
  1031.      of the first free block in our storage buffer, relative to the ES
  1032.      register. Since the actual shifting of bytes is a task that is likely
  1033.      to need doing on other occasions, we have dedicated a special routine
  1034.      to it, SCREENCOPY.  This is listed in the new version of DISPLAY.ASM
  1035.      which is provided with issue.
  1036.      
  1037.      Pop_18:
  1038.                      call    ScreenCopy              ; Copy word from screen
  1039.                      loop    Pop_18                  ; For length of row
  1040.      
  1041.      The LOOP instruction decrements the value in CX and then returns
  1042.      control to the statement following the indicated label.  It does this
  1043.      until the value of CX is reduced to zero, after which the program
  1044.      falls through to the next line.  Since CX was originally loaded with
  1045.      
  1046.      The QBNews                                                     Page 16
  1047.      Volume  2, Number  4                                 December 22, 1991
  1048.  
  1049.      the number of columns in the row, this only happens when a full row
  1050.      has been copied.
  1051.      
  1052.      Back in the outer loop, we retrieve the original screen pointer from
  1053.      the stack and add the row increment value which we calculated earlier
  1054.      to it. This value may be 80, 160 or 264 bytes, depending upon whether
  1055.      our screen is set to 40, 80 or 132 columns, and will make SI point to
  1056.      the start of the next row to be moved. We also retrieve the row count
  1057.      into CX, so that the second  LOOP instruction can be used to repeat
  1058.      the whole procedure until every row has been done.
  1059.      
  1060.                      pop     si                      ; Bump pointer
  1061.                      add     si,cs:Increment         ;    to next row
  1062.                      pop     cx                      ; Recover row count
  1063.                      loop    Pop_17                  ; For each row
  1064.      
  1065.      That's the first stage complete.  We have copied the whole of the
  1066.      rectangle which our window will cover, into the storage buffer
  1067.      reserved for it.  It's time to start drawing the window, but before
  1068.      we start we must put our segment registers back where they belong.
  1069.      
  1070.                      push    es                      ; Realign Code and
  1071.                      pop     ds                      ;    Data segments
  1072.      
  1073.      If our caller has requested it, we must ZOOM the window onto the
  1074.      screen.  This means drawing not just one window, but a whole raft of
  1075.      them, each a little larger than the last, until we have one of the
  1076.      size required.  In assembly-language, the whole process is so fast
  1077.      that it seems to occur in one continuous movement.  It is so fast, in
  1078.      fact, that we will  have to deliberately slow it down, so that our
  1079.      audience can appreciate it.
  1080.      
  1081.      The routine which does all this deserves more than just a bit-part in
  1082.      this article so I have given it a wider audience by making it a self-
  1083.      contained procedure in its' own right, one which can be called direct
  1084.      from QuickBASIC.  You can find the source code for EXPLODE in the new
  1085.      version of DISPLAY.ASM which is provided with this issue.  For now we
  1086.      just need to be aware that it is designed to use QuickBASIC's calling
  1087.      conventions, so we had better emulate them and pass our arguments  on
  1088.      the stack.
  1089.      
  1090.                      xor     ah,ah                   ; Pass all parameters in AX
  1091.                      mov     al,TlRow                ; Get upper-left row
  1092.                      inc     al                      ; Must use BASIC numbering
  1093.                      push    ax                      ; Pass the argument
  1094.                      mov     al,TlCol                ; Get upper-left column
  1095.                      inc     al                      ; Must use BASIC numbering
  1096.                      push    ax                      ; Pass the argument
  1097.                      mov     al,BrRow                ; Get lower-right row
  1098.                      inc     al                      ; Must use BASIC numbering
  1099.                      push    ax                      ; Pass the argument
  1100.                      mov     al,BrCol                ; Get lower-right column
  1101.                      inc     al                      ; Must use BASIC numbering
  1102.                      push    ax                      ; Pass the argument
  1103.      
  1104.      The QBNews                                                     Page 17
  1105.      Volume  2, Number  4                                 December 22, 1991
  1106.  
  1107.                      push    [bp+12]                 ; Pass display attribute
  1108.                      push    [bp+6]                  ; Pass speed value
  1109.                      call    Explode                 ; Zoom the window
  1110.      
  1111.      Notice that we are calling EXPLODE, even if our caller specified no
  1112.      ZOOM for this window. This is not a mistake since no ZOOM is the same
  1113.      as a delay count of zero and just means that the window panel will
  1114.      appear instantaneously. 
  1115.      
  1116.      We're not finished yet, though. We've still got to draw the border.
  1117.      That is, if one is required. 
  1118.      
  1119.                      mov     ax,Ulc                  ; Get row/column co-ordinate
  1120.                      call    ScreenAddress           ; Convert to memory address
  1121.                      cmp     byte ptr [bp+10],0      ; Border required?
  1122.                      ja      Pop_19                  ; Yes, draw it
  1123.                      jmp     Pop_23                  ; Else check for shadow
  1124.      
  1125.      The border type was the sixth parameter supplied by the calling
  1126.      program.  We'll load it into BX so that it can be used as an index to
  1127.      the list of graphics characters stored in our local data. While we're
  1128.      at it, we'll load the display attribute required into AH
  1129.       
  1130.      Pop_19:
  1131.                      xor     bx,bx                   ; Border type
  1132.                      mov     bl,[bp+10]              ;    to BX
  1133.                      mov     ah,[bp+12]              ; Attribute to AH
  1134.                      push    di                      ; Save screen offset
  1135.      
  1136.      We've already worked out the address of the top-left corner of the
  1137.      window, no need to calculate it again. Now to get the top-left corner
  1138.      character.
  1139.      
  1140.      TopLeft         label byte
  1141.                      db    ' ┌╔╒╓╤╦┬╥'       ; TL Corner characters
  1142.      
  1143.      Border types   -->     012345678     <--  Argument in BX
  1144.                   
  1145.      TOPLEFT is a label which refers to the string of extended ASCII top-
  1146.      left characters in our program data.  Since BX has been set to the
  1147.      number of the border type required, we can use it as an index into
  1148.      the character string.  The following instruction, therefore, loads AL
  1149.      with the byte stored at the address pointed to by TOPLEFT + BX.
  1150.      
  1151.                      mov     al,TopLeft[bx]          ; Border character to AL
  1152.                      call    ScreenWrite             ; Send it to the screen
  1153.      
  1154.      Once the character is in AL we can send it, and the attribute byte in
  1155.      AH, to the screen address pointed to by ES:DI. Our old friend, the
  1156.      SCREENWRITE procedure performs this for us.
  1157.      
  1158.      We now have to put the horizontal border characters along the top row
  1159.      of the window.  Since SCREENWRITE updates the screen pointer after it
  1160.      writes a character, DI is already pointing to the correct address for
  1161.      
  1162.      The QBNews                                                     Page 18
  1163.      Volume  2, Number  4                                 December 22, 1991
  1164.  
  1165.      us to continue.
  1166.      
  1167.                      mov     al,Horizontal[bx]       ; Border character to AL
  1168.                      xor     cx,cx                   ; Window width
  1169.                      mov     cl,[bp+14]              ;    to CX
  1170.                      dec     cl                      ; Subtract
  1171.                      dec     cl                      ;    corners
  1172.      
  1173.      Load the appropriate ASCII value into AL, and the number of
  1174.      characters to write into CX, we can do this with a loop.
  1175.      
  1176.      Pop_20:
  1177.                      call    ScreenWrite             ; Send it to the screen
  1178.                      loop    Pop_20
  1179.      
  1180.      Finally, load the Right-hand corner character and send that out as
  1181.      well. That's the top row done.
  1182.      
  1183.                      mov     al,TopRight[bx]         ; Border character to AL
  1184.                      call    ScreenWrite             ; Send it to the screen
  1185.      
  1186.      Can we do the horizontal borders as a loop?  'course we can!
  1187.      
  1188.                      pop     di                      ; Recover offset
  1189.                      add     di,Increment            ; Bump to next row
  1190.                      mov     al,Vertical[bx]         ; Border character to AL
  1191.                      mov     cl,[bp+16]              ; Window height to CX
  1192.                      dec     cl                      ; Subtract top and
  1193.                      dec     cl                      ;    bottom rows
  1194.      
  1195.      Fortunately, we had the foresight to store our original starting
  1196.      position on the screen before SCREENWRITE incremented it. All we need
  1197.      do is retrieve it and add our increment value to point DI to the
  1198.      start of the next row.  Let's get the correct vertical bar character
  1199.      into AL and then set CX to control the number of rows we are going to
  1200.      loop through. We've already done the top row and the bottom will
  1201.      be different again, so we can subtract two from the total number
  1202.      of rows.
  1203.      
  1204.      Pop_21:
  1205.                      push    cx                      ; Save counter
  1206.                      push    di                      ; Save screen pointer
  1207.                      call    ScreenWrite             ; Left border
  1208.                      mov     cl,[bp+14]              ; Get window width
  1209.                      dec     cl                      ; Point
  1210.                      dec     cl                      ;    to      
  1211.                      shl     cx,1                    ;      rightmost
  1212.                      add     di,cx                   ;        column
  1213.                      call    ScreenWrite             ; Right border
  1214.                      pop     di                      ; Recover screen pointer
  1215.                      add     di,Increment            ; Bump to next row
  1216.                      pop     cx                      ; Recover row count
  1217.                      loop    Pop_21                  ; For each row
  1218.      
  1219.      
  1220.      The QBNews                                                     Page 19
  1221.      Volume  2, Number  4                                 December 22, 1991
  1222.  
  1223.      There, that's done. Notice how we used CX again, in the middle of the
  1224.      loop, to calculate the offset of the character at the right border.
  1225.      Finally, apart from the corner characters, the bottom row is a repeat
  1226.      of the top ....
  1227.      
  1228.                      mov     al,BotLeft[bx]          ; Border character to AL
  1229.                      call    ScreenWrite             ; Send it to the screen
  1230.                      mov     al,Horizontal[bx]       ; Border character to AL
  1231.                      mov     cl,[bp+14]              ; Get window width
  1232.                      dec     cl                      ; Subtract
  1233.                      dec     cl                      ;    corners
  1234.      Pop_22:
  1235.                      call    ScreenWrite             ; Send it to the screen
  1236.                      loop    Pop_22
  1237.                      mov     al,BotRight[bx]         ; Border character to AL
  1238.                      call    ScreenWrite             ; Send it to the screen
  1239.      
  1240.      ... and that's the window AND border done. Is that the lot?
  1241.      
  1242.      Pop_23:
  1243.                      cmp     byte ptr [bp+8],0       ; Shadow required?
  1244.                      ja      Pop_24                  ; Yes, handle it
  1245.                      jmp     Pop_37                  ; Else wrap everything up
  1246.      
  1247.      Not quite, we've still got to deal with SHADOW.
  1248.      
  1249.      Pop_24:
  1250.                      mov     di,WinTop               ; Back to top-left corner
  1251.                      add     di,Increment            ;    start at next row down
  1252.                      xor     cx,cx                   ; Get window width
  1253.                      mov     cl,[bp+14]              ;    into CX
  1254.                      shl     cx,1                    ; Include attribute bytes
  1255.                      cmp     byte ptr [bp+8],2       ; Solid shadow?
  1256.                      ja      Pop_30                  ; No, make it transparant
  1257.                      cmp     byte ptr [bp+8],1       ; Left shadow?
  1258.                      jne     Pop_25                  ; No, must be right
  1259.                      dec     di                      ; Left one
  1260.                      dec     di                      ;    column
  1261.                      jmp     short Pop_26            ; Get to work
  1262.      
  1263.      To give the right three-dimensional effect, the shadow will have to
  1264.      begin one row down from the top of the window.  Is it going to be a
  1265.      LEFT or a RIGHT shadow?  If LEFT, we point DI one column to the Left
  1266.      of the window, if RIGHT, then DI is set to point one column past the
  1267.      Right-hand border.  We must also distinguish between transparent and
  1268.      solid shadow. The first changes the display attribute of the shadowed
  1269.      text so that it is still visible, albeit dimly, while the second type
  1270.      blanks out underlying characters completely.
  1271.      
  1272.      Pop_25:
  1273.                      add     di,cx                   ; Offset past right-hand edge
  1274.      Pop_26:
  1275.                      mov     ax,720h                 ; Space on black background
  1276.                      push    cx                      ; Save width for now
  1277.      
  1278.      The QBNews                                                     Page 20
  1279.      Volume  2, Number  4                                 December 22, 1991
  1280.  
  1281.                      mov     cl,[bp+16]              ; Rows to shadow
  1282.      
  1283.      The display attribute we will use for solid shadow is 7. Normal white
  1284.      text on a black background. The actual character is ASCII 32(20 Hex),
  1285.      a blank space.  The shadow will be as tall as the window itself, but,
  1286.      since we start one row down, it will extend past the bottom.  Here's
  1287.      the loop that does it.
  1288.      
  1289.      Pop_27:
  1290.                      call    ScreenWrite             ; Send it to the screen
  1291.                      add     di,Increment            ; Bump to
  1292.                      dec     di                      ;    next
  1293.                      dec     di                      ;      row 
  1294.                      loop    Pop_27                  ; For height of window
  1295.                      pop     cx                      ; Recover window width
  1296.                      sub     di,Increment            ; Back up one row
  1297.                      cmp     byte ptr [bp+8],1       ; Left shadow?
  1298.                      je      Pop_28                  ; No, must be right
  1299.                      sub     di,cx                   ; Jump back to start of row
  1300.                      inc     di                      ; Begin one column
  1301.                      inc     di                      ;    in from left
  1302.      Pop_28:
  1303.                      shr     cx,1                    ; Convert width to columns
  1304.      Pop_29:
  1305.                      call    ScreenWrite             ; Put black shadow
  1306.                      loop    Pop_29                  ;    under the bottom row
  1307.                      jmp     short Pop_37            ; Branch to the exit
  1308.      
  1309.      If we're doing TRANSPARENT shadow, then we don't change the character
  1310.      byte at all. Instead we skip to the attribute byte and change that to
  1311.      a value of eight which produces dark grey text on a black background.
  1312.      WRITEBYTE is a variant of  SCREENWRITE which outputs a single byte to
  1313.      the display instead of a character and attribute (2 bytes). Once more
  1314.      it is listed for you in the new version of DISPLAY.ASM.
  1315.      
  1316.      Pop_30:
  1317.                      cmp     byte ptr [bp+8],3       ; Left shadow?
  1318.                      jne     Pop_31                  ; No, must be right
  1319.                      dec     di                      ; Left one
  1320.                      dec     di                      ;    column
  1321.                      jmp     short Pop_32            ; Get to work
  1322.      Pop_31:
  1323.                      add     di,cx                   ; Offset past right-hand edge
  1324.      Pop_32:
  1325.                      inc     di                      ; Bump to attribute byte
  1326.                      mov     al,8                    ; Dark grey foreground
  1327.                      push    cx                      ; Save width for now
  1328.                      mov     cl,[bp+16]              ; Rows to shadow
  1329.      Pop_33:
  1330.                      call    WriteByte               ; Set display attribute
  1331.                      add     di,Increment            ; Bump to
  1332.                      dec     di                      ;    next row
  1333.                      loop    Pop_33                  ; For height of window
  1334.                      pop     cx                      ; Recover window width
  1335.      
  1336.      The QBNews                                                     Page 21
  1337.      Volume  2, Number  4                                 December 22, 1991
  1338.  
  1339.                      sub     di,Increment            ; Back up
  1340.                      inc     di                      ;    one row
  1341.                      cmp     byte ptr [bp+8],3       ; Left shadow?
  1342.                      jne     Pop_34                  ; No, must be right
  1343.                      dec     di                      ; Back up one column
  1344.                      jmp     short Pop_35            ; Start on bottom row
  1345.      Pop_34:
  1346.                      sub     di,cx                   ; Jump back to the
  1347.                      inc     di                      ;    beginning of the row
  1348.      Pop_35:
  1349.                      shr     cx,1                    ; Convert width to columns
  1350.      Pop_36:
  1351.                      call    WriteByte               ; Set display attribute
  1352.                      inc     di                      ; Bump past character byte    
  1353.                      loop    Pop_36                  ; For width of window
  1354.      
  1355.      And there it is.  One window up on the screen, just as our calling
  1356.      program requested.  All that remains is to tidy up and go home.
  1357.      
  1358.      Pop_37:
  1359.                      xor     ax,ax                   ; Report no error
  1360.      Pop_38:
  1361.                      pop     si                      ; Clean up the stack
  1362.                      pop     di
  1363.                      pop     es
  1364.                      pop     ds
  1365.                      pop     bp
  1366.                      ret     16                      ; Return to caller
  1367.      PopUp           endp
  1368.      
  1369.      There you are.  A superfast, full feature window generator, worthy to
  1370.      stand alongside all those professional programs on your shelf.  Bill
  1371.      Gates, move over!
  1372.      
  1373.      Wait a minute though, it's all very well being able to pop up windows
  1374.      of all shapes and sizes.  We also need to take them down again, when
  1375.      they're finished with.  And what about all that screen data stored in
  1376.      the buffer?  We've still got to put it back again.
  1377.      
  1378.      That's the business of the second routine in this module.  Luckily,
  1379.      SHUTUP is not nearly as big as POPUP.  All it has to do, in fact, is
  1380.      find the last block of screen data that was stored in the buffer, and
  1381.      restore it to it's original location on the screen, wiping out the
  1382.      window which covers it in the process.  To make it a little fancier,
  1383.      however, I have added a reverse ZOOM option. This restores the screen
  1384.      data stored in the buffer selectively, working from the outside edge
  1385.      of the window inwards to the centre.  This makes the window appear to
  1386.      implode and, depending on the speed parameter supplied, gives a very
  1387.      slick effect.
  1388.      
  1389.      ShutUp          proc    far
  1390.                      push    bp                      ; Save base pointer
  1391.                      mov     bp,sp                   ; Establish stack frame
  1392.                      push    ds                      ; Save segment
  1393.      
  1394.      The QBNews                                                     Page 22
  1395.      Volume  2, Number  4                                 December 22, 1991
  1396.  
  1397.                      push    es                      ;    registers and
  1398.                      push    di                      ;      index
  1399.                      push    si                      ;        pointers
  1400.                      push    cs                      ; Align code and
  1401.                      pop     ds                      ;    data segments
  1402.                      cld                             ; Clear direction forward
  1403.                      call    VideoType               ; Get video parameters
  1404.                      mov     al,ah                   ; Transfer number of
  1405.                      xor     ah,ah                   ;    columns to AX
  1406.                      shl     ax,1                    ;      and convert
  1407.                      mov     Increment,ax            ;        to bytes
  1408.      
  1409.      SHUTUP is  going to access the same data that was used by POPUP and,
  1410.      since this is at the top of our local code, we must begin by pointing
  1411.      the DS:SI registers to it.
  1412.      
  1413.                      mov     si,offset Buffer        ; DS:SI==> screen buffer
  1414.                      xor     ax,ax                   ; Initialise
  1415.                      push    ax                      ;    back pointer
  1416.      
  1417.      By default, all offset addresses are relative to the DS register. Now
  1418.      we have it positioned correctly, we can set SI to point to the start
  1419.      of the save buffer.  What follows is very similar to the routine, in
  1420.      POPUP, which searched for  the next free block.  Remember that the
  1421.      first two bytes of each block is a pointer to the next one in the
  1422.      chain.  Just as before, we must follow the pointers along until we
  1423.      come to an empty block.
  1424.      
  1425.      Shut_01:
  1426.                      cmp     word ptr [si],0         ; Is anything there?
  1427.                      jz      Shut_02                 ; No, we're at the end
  1428.                      pop     ax                      ; Retrieve pointer
  1429.                      push    si                      ; Save present pointer
  1430.                      mov     si,[si]                 ; Point to next block
  1431.                      jmp     Shut_01                 ; Keep searching
  1432.      
  1433.      If the first word in the buffer is zero, of course, the buffer is
  1434.      empty and there are no windows to restore, (it was a waste of  time
  1435.      calling us!).  Otherwise we end up pointing to the the first free
  1436.      block.
  1437.      
  1438.      Hang on a minute, though, it's the PREVIOUS block we want.  The one
  1439.      which contains the last window stored ...
  1440.      
  1441.      Oh, I get it!  That's why we kept saving the next-block-pointer.  All
  1442.      we have to do is pop the last one back into SI, and we're pointing to
  1443.      the previous block again.
  1444.      
  1445.      Shut_02:
  1446.                      pop     si                      ; Retrieve last pointer
  1447.                      cmp     si,0                    ; Was there anything?
  1448.                      jnz     Shut_03                 ; Yes, proceed
  1449.                      mov     ax,1                    ; Else set Errorlevel
  1450.                      jmp     Shut_14                 ;    and abort
  1451.      
  1452.      The QBNews                                                     Page 23
  1453.      Volume  2, Number  4                                 December 22, 1991
  1454.  
  1455.      
  1456.      Right, we've found the block to be restored.  Now we need to extract
  1457.      the screen location where it's going to be restored to and the height
  1458.      and width of the original rectangle. If you remember they were stored
  1459.      along with the original data.  Notice we store the window  dimensions
  1460.      twice, once for reference and once for use as a running total as we
  1461.      gradually restore larger portions of the screen below the window.
  1462.      
  1463.      Shut_03:
  1464.                      mov     BuffPtr,si              ; Save buffer pointer
  1465.                      inc     si                      ; Bump to
  1466.                      inc     si                      ;    next entry
  1467.                      mov     di,[si]                 ; ES:DI==> screen location
  1468.                      mov     WinTop,di               ; Save screen offset
  1469.                      inc     si                      ; Bump to
  1470.                      inc     si                      ;    next entry
  1471.                      mov     ax,[si]                 ; Get panel dimensions
  1472.                      mov     Area,ax                 ; Store them
  1473.                      mov     ToDo,ax                 ;    for later
  1474.                      inc     si                      ; Bump to screen
  1475.                      inc     si                      ;    storage block
  1476.                      mov     BuffTop,si              ; Save buffer pointer
  1477.      
  1478.      The next step is to calculate the addresses in the save buffer and on
  1479.      the screen, where the LAST row of the window begins.  Using this and
  1480.      the address of the first row, which SI is now pointing to, as our
  1481.      starting points, we can work inwards on successive passes through the
  1482.      restore loop.
  1483.        
  1484.                      xor     bx,bx                   ; AX has window width
  1485.                      xchg    bl,ah                   ; BX has window height
  1486.                      dec     bl                      ;    less one row
  1487.                      shl     bx,1                    ; Convert to bytes
  1488.                      mul     bx                      ; Calculate offset
  1489.                      add     ax,si                   ;    of the last row
  1490.                      mov     BuffEnd,ax              ; Save this as well
  1491.                      mov     ax,Increment            ; Multiply screen width
  1492.                      mov     bl,Height               ;    by window height
  1493.                      dec     bl                      ;      less one row
  1494.                      mul     bx                      ; Result is relative offset
  1495.                      add     ax,di                   ; Now convert it to the
  1496.                      mov     WinEnd,ax               ;    absolute screen offset
  1497.      
  1498.      Before beginning, we still need to point ES to the segment address of
  1499.      video memory and, as ever, SCREENADDRESS proves its' worth.  We had
  1500.      better check that the delay count supplied is legal as well, just in
  1501.      case the user has passed a negative argument or something silly like
  1502.      that. Since we are treating the argument as an unsigned value a delay
  1503.      of -1 would be interpreted as 65535 which, even at assembler speeds,
  1504.      would take a L-O-N-G time.
  1505.      
  1506.                      xor     ax,ax                   ; Get video segment
  1507.                      call    ScreenAddress           ;    and CRT status port
  1508.                      cmp     word ptr [bp+6],0       ; Check for
  1509.      
  1510.      The QBNews                                                     Page 24
  1511.      Volume  2, Number  4                                 December 22, 1991
  1512.  
  1513.                      jge     Shut_04                 ;    legal delay
  1514.                      mov     word ptr [bp+6],20      ;      values
  1515.      
  1516.      Here is the start of the outer loop.  We begin by loading our source
  1517.      and destination index registers, SI and DI, with the addresses of the
  1518.      top row of the window, SI being its' location in the save buffer and
  1519.      DI it's location on the screen.  CX, which, as always, is the loop
  1520.      counter, gets the number of columns in this row.
  1521.      
  1522.      Shut_04:
  1523.                      xor     cx,cx                   ; Clear counter
  1524.                      mov     si,BuffTop              ; DS:SI==> first buffer row
  1525.                      mov     di,WinTop               ; ES:DI==> first screen row
  1526.                      mov     cl,Cols2do              ; Number of words to copy
  1527.      
  1528.      We have already met SCREENCOPY above, when we used it to copy  words
  1529.      from the display into our save buffer.  Now  we are using it in the
  1530.      opposite direction to move the data from the buffer back onto the
  1531.      screen, repeating the process until each column of this row has been
  1532.      restored.
  1533.      
  1534.      Shut_05:
  1535.                      call    ScreenCopy              ; Send word to the screen
  1536.                      loop    Shut_05                 ; For length of row
  1537.                      dec     Rows2do                 ; All rows done?
  1538.                      jnz     Shut_06                 ; No, carry on
  1539.                      jmp     Shut_13                 ; Otherwise depart
  1540.      
  1541.      With this row restored we can decrement the count of rows to be done.
  1542.      Eventually, when the count reduces to zero and sets the zero flag in
  1543.      the flags register, we can take it as a sign that the window has gone
  1544.      and branch out of the loop.  
  1545.      
  1546.      For now, however, we must carry on and restore the bottom row, using
  1547.      very similar code to the top row routine, except that our index
  1548.      registers are pointed to the bottom row of the window, both in the
  1549.      buffer and on the screen.
  1550.      
  1551.      Shut_06:
  1552.                      mov     si,BuffEnd              ; DS:SI==> last buffer row
  1553.                      mov     di,WinEnd               ; ES:DI==> last screen row
  1554.                      mov     cl,Cols2do              ; Number of words to copy
  1555.      Shut_07:
  1556.                      call    ScreenCopy              ; Send word to the screen
  1557.                      loop    Shut_07                 ; For length of row
  1558.                      dec     Rows2do                 ; All rows done?
  1559.                      jnz     Shut_08                 ; No, carry on
  1560.                      jmp     Shut_13                 ; Otherwise depart
  1561.      
  1562.      A few sums to do now.  We must take the our pointer to the start of
  1563.      the save buffer, BUFFTOP, and add to it the number of bytes per row
  1564.      of the window we are restoring.  This will bump the buffer pointer to
  1565.      the next row to be restored at the top of the window. Subtracting the
  1566.      same number of bytes from BUFFEND, the last row pointer, will give us
  1567.      
  1568.      The QBNews                                                     Page 25
  1569.      Volume  2, Number  4                                 December 22, 1991
  1570.  
  1571.      the buffer location of the next row to be restored at the bottom of
  1572.      the window. 
  1573.      
  1574.      Shut_08:
  1575.                      xor     ax,ax                   ; Clear AX
  1576.                      mov     si,BuffTop              ; Reset
  1577.                      mov     al,Breadth              ;    pointer
  1578.                      shl     al,1                    ;      to first
  1579.                      add     si,ax                   ;        buffer
  1580.                      mov     BuffTop,si              ;          row
  1581.                      mov     di,WinTop               ; Do the same
  1582.                      add     di,Increment            ;   for the first
  1583.                      mov     WinTop,di               ;     screen row
  1584.      
  1585.      A similar calculation must be made to obtain the screen addresses of
  1586.      the new top and bottom rows.  However this is a little easier, since 
  1587.      all we need do is add or subtract the row INCREMENT from the current
  1588.      pointer values.
  1589.      
  1590.                      mov     ax,BuffEnd              ; Reset
  1591.                      mov     cl,Breadth              ;    pointer
  1592.                      shl     cl,1                    ;      to last
  1593.                      sub     ax,cx                   ;        buffer
  1594.                      mov     BuffEnd,ax              ;          row
  1595.                      mov     ax,WinEnd               ; Do the same
  1596.                      sub     ax,Increment            ;   for the last
  1597.                      mov     WinEnd,ax               ;     screen row
  1598.      
  1599.      We've removed the top and bottom rows, now it's time to do the left
  1600.      and right columns. After our previous calculations, SI and DI are now
  1601.      pointing to the top-left corner of what's left of the window, so we
  1602.      can start by removing the column on the left-hand side.
  1603.      
  1604.      Shut_09:                
  1605.                      cmp     si,BuffEnd              ; End of buffer?
  1606.                      ja      Shut_10                 ; Yes, see if we've finished
  1607.                      call    ScreenCopy              ; Send word to the screen
  1608.                      xor     ax,ax                   ; Clear AX again
  1609.                      mov     al,Breadth              ; Keep
  1610.                      dec     al                      ;    doing
  1611.                      shl     al,1                    ;      it
  1612.                      add     si,ax                   ;        all
  1613.                      add     di,Increment            ;          down
  1614.                      dec     di                      ;            the
  1615.                      dec     di                      ;              left
  1616.                      jmp     short Shut_09           ;                side
  1617.      Shut_10:
  1618.                      dec     Cols2do                 ; All columns done?
  1619.                      jz      Shut_13                 ; If so, depart
  1620.                      mov     al,Cols2do              ; How far to the
  1621.                      shl     al,1                    ;    end of the row?
  1622.                      mov     si,BuffTop              ; Point SI to
  1623.                      add     si,ax                   ;    buffer data
  1624.                      mov     di,WinTop               ; Point DI to
  1625.      
  1626.      The QBNews                                                     Page 26
  1627.      Volume  2, Number  4                                 December 22, 1991
  1628.  
  1629.                      add     di,ax                   ;    screen offset
  1630.      
  1631.      This done, we decrement the number of columns there are to do and, as
  1632.      long as the remainder is not zero, add the result converted to bytes,
  1633.      to our index registers. This points us to the top-right corner of the
  1634.      window, which is now shrinking fast.
  1635.      
  1636.      Away we go down the right-hand side......
  1637.      
  1638.      Shut_11:
  1639.                      call    ScreenCopy              ; Send word to the screen
  1640.                      cmp     si,BuffEnd              ; End of buffer?
  1641.                      ja      Shut_12                 ; Yes, see if we're finished
  1642.                      xor     ax,ax                   ; Clear AX again
  1643.                      mov     al,Breadth              ; Keep
  1644.                      dec     al                      ;    doing
  1645.                      shl     al,1                    ;      it
  1646.                      add     si,ax                   ;        all
  1647.                      add     di,Increment            ;          down
  1648.                      dec     di                      ;            the
  1649.                      dec     di                      ;              right
  1650.                      jmp     short Shut_11           ;                side
  1651.      Shut_12:
  1652.                      dec     Cols2do                 ; All columns done?
  1653.                      jz      Shut_13                 ; If so, depart
  1654.      
  1655.      By the time we reach here we have done a complete circuit of our
  1656.      window, lopping off a row or column on all four sides.  If there is
  1657.      still more to do, we must adjust the pointers to the screen and 
  1658.      buffer to take the reduced number of columns into account.
  1659.      
  1660.                      add     BuffTop,2               ; Each subsequent
  1661.                      add     BuffEnd,2               ;    row starts
  1662.                      add     WinTop,2                ;      another word
  1663.                      add     WinEnd,2                ;        further in
  1664.      
  1665.      Although it has taken a long time to describe, the actual processing
  1666.      has been done in less than the twinkle of an eye.  So if we want our
  1667.      audience to actually see how clever we are, we must slow things down
  1668.      to human speeds. It's a good job computers don't get bored....
  1669.      
  1670.                      push    [bp+6]                  ; Pass speed value
  1671.                      call    Delay                   ; Pause awhile
  1672.                      jmp     Shut_04                 ; Then do it all again
  1673.      
  1674.      Well, to us it didn't take long. The window is gone and the screen is
  1675.      back in its' original pristine state.  Only one thing left to do.  We
  1676.      must also clear this block from our save buffer.
  1677.      
  1678.      With our usual foresight, we saved the pointer to the start of the
  1679.      block.  Notice that we're retrieving it into DI, this time, not SI. 
  1680.      There's a good reason for this which I'll explain in a minute. In the
  1681.      meantime, we need to calculate the length of the block, since we're
  1682.      going to set every byte to zero.
  1683.      
  1684.      The QBNews                                                     Page 27
  1685.      Volume  2, Number  4                                 December 22, 1991
  1686.  
  1687.      
  1688.      Shut_13:
  1689.                      mov     di,BuffPtr              ; Recover buffer pointer
  1690.                      mov     cx,[di]                 ; Pointer to next block
  1691.                      sub     cx,di                   ; Calculate length of block
  1692.                      inc     cx
  1693.      
  1694.      Amongst the many instructions built into the Intel 8086 series of
  1695.      microprocessors, is a set of very powerful string manipulation
  1696.      routines. The one we're going to use is STOSB (STOre Byte to String).
  1697.      This stores the contents of the AL register into the address pointed
  1698.      to by the ES:DI registers, and then increments DI.  Not much in
  1699.      itself, but if you prefix STOSB with the REP instruction, the process
  1700.      is REPeated the number of times set by CX.  This way we can fill a
  1701.      large block of memory with a single line of code, instead of having
  1702.      to set up an elaborate loop.
  1703.      
  1704.      ES:DI is already pointing to the start of the block.  CX contains the
  1705.      length of the block, in bytes.  All we have to do is load AL with the
  1706.      value to fill, in this case zero.
  1707.      
  1708.                      push    ds                      ; Point ES to Data Segment
  1709.                      pop     es
  1710.                      xor     ax,ax                   ; Clear AX
  1711.                      rep     stosb                   ; Zero restored block
  1712.      Shut_14:
  1713.                      pop     si                      ; Clean up the stack
  1714.                      pop     di
  1715.                      pop     es
  1716.                      pop     ds
  1717.                      pop     bp
  1718.                      ret     2                       ; Return to caller
  1719.      ShutUp          endp
  1720.      
  1721.                      end
  1722.      
  1723.      That's it, we've done it.  Our window manager is complete.  Have you
  1724.      still got the strength to add it to the library?
  1725.      
  1726.       ┌──────────────────────────────────────────────────────────────────┐
  1727.       │              GETTING IT ALL TOGETHER                             │
  1728.       └──────────────────────────────────────────────────────────────────┘
  1729.      
  1730.      Two object files are provided.  WINDOWER.OBJ contains the two  window
  1731.      procedures, POPUP and SHUTUP, described above.  DISPLAY.OBJ contains
  1732.      FASTPRINT and all the support routines used in my previous article,
  1733.      along with EXPLODE and the other new routines added in this issue.
  1734.      
  1735.      If you are using the BASIC 7 PDS with far strings, then, instead of
  1736.      DISPLAY.OBJ you must use the object file DISPLAY7.OBJ which is also
  1737.      provided.  WINDOWER will work equally well with either version of the
  1738.      compiler.
  1739.      
  1740.      To produce a new version of your Assembly-Language Library, copy the
  1741.      
  1742.      The QBNews                                                     Page 28
  1743.      Volume  2, Number  4                                 December 22, 1991
  1744.  
  1745.      object files along with your existing copy of ASSEMBLY.LIB into the
  1746.      directory which contains the QuickBASIC Library Manager LIB.EXE. Then
  1747.      issue the following command:
  1748.      
  1749.          LIB ASSEMBLY +WINDOWER -+DISPLAY,ASSEMBLY.CAT;
  1750.          
  1751.      This will add WINDOWER.OBJ to ASSEMBLY.LIB and replace the existing
  1752.      version of DISPLAY.OBJ with the new one.  The command also tells 
  1753.      LIB.EXE to generate an updated version of your library catalogue file
  1754.      ASSEMBLY.CAT.
  1755.      
  1756.      Producing the matching Quick Library, ASSEMBLY.QLB, is just as easy.
  1757.      Using the new copy of ASSEMBLY.LIB you have just produced, type:
  1758.      
  1759.          LINK /QU ASSEMBLY.LIB,,,BQLB45.LIB;
  1760.      
  1761.      Notice that LINK.EXE can work just as easily with complete libraries
  1762.      as it does with individual object files.  The Quick Library support
  1763.      file BQLB45.LIB, (QBXQLB.LIB if you use BASIC 7), must also be either
  1764.      present, or on your environment search path.
  1765.      
  1766.      To use the window routines in your programs you must include the
  1767.      following declarations at the beginning of the source code:
  1768.      
  1769.      DECLARE SUB PopUp(BYVAL Row%, BYVAL Col%, BYVAL Hght%, BYVAL Wdth%,_
  1770.                        BYVAL Attr%, BYVAL Brdr%, BYVAL Shdw%, BYVAL Zoom%)
  1771.      DECLARE SUB ShutUp(BYVAL Speed%)
  1772.          
  1773.      Then, whenever you want to pop up a window, issue a statement like
  1774.      this ...
  1775.      
  1776.              PopUp 4, 10, 8, 50, 48, 2, 1, -1
  1777.          
  1778.              .. which produces a window with its top-left corner at row 4,
  1779.      column 10.  This window is eight rows high by fifty columns wide, it
  1780.      has a black, double-lined border on a cyan background (if you have a
  1781.      colour monitor), with shadow on the left-hand side and, when it
  1782.      appears, it will Z-O-O-O-M onto the screen. You can pass variables or
  1783.      expressions to POPUP as well as constants, but they must all evaluate
  1784.      to integers, otherwise the routine may refuse to pop!
  1785.      
  1786.      To get rid of the window, and restore the screen contents, use the
  1787.      statement ...
  1788.      
  1789.              ShutUp -1
  1790.          
  1791.      Remember that SHUTUP always removes the most recent window.
  1792.      
  1793.      EXPLODE, the external procedure which is called by POPUP to zoom  the
  1794.      window onto the screen, can also be called directly from QuickBASIC.
  1795.      If you want to use it outside of the window routines, then you must
  1796.      declare it seperately with the following statement:
  1797.      
  1798.      DECLARE SUB Explode (BYVAL Y1%, BYVAL X1%, BYVAL Y2%, BYVAL X2%,_
  1799.      
  1800.      The QBNews                                                     Page 29
  1801.      Volume  2, Number  4                                 December 22, 1991
  1802.  
  1803.                           BYVAL Attr%, BYVAL Speed%)
  1804.      
  1805.      Arguments:  Y1%         =  Upper-left row of rectangle to be cleared
  1806.                  X1%         =  Upper-left column of rectangle
  1807.                  Y2%         =  Lower-right row of rectangle
  1808.                  X2%         =  Lower-right column of rectangle
  1809.                  Attr%       =  Display attribute or colour that rectangle
  1810.                                 should be cleared to 
  1811.                  Speed%      =  Speed (in milliseconds) of explosion.
  1812.      
  1813.      The example  program, WINDEM.BAS, uses EXPLODE whenever it needs to
  1814.      clear text from a window on the screen, without having to remove the
  1815.      window itself.  It can also be used as a general-purpose routine for
  1816.      whenever you need to selectively clear a part of the screen, without
  1817.      effecting the rest of the display. You can disable the exploding part
  1818.      by specifying a delay of zero.
  1819.      
  1820.      ─────────────────────────────────────────────────────────────────────
  1821.      If you have a problem getting these routines to work with your
  1822.      system, or you have any comments or suggestions for future programs,
  1823.      I would like to hear from you.
  1824.      
  1825.                      Christy Gemmell
  1826.                      22 Peake Road, Northfields
  1827.                      Leicester LE4 7DN
  1828.                      England
  1829.                      Tel. (044)-0533-767960
  1830.      
  1831.      If transatlantic mail is too slow (or the telephone charge too high),
  1832.      I can also be reached via:
  1833.      
  1834.                      Jim Kreyling (sysop)
  1835.                      Club-PC BBS
  1836.                      1217 Crescent Drive,
  1837.                      Smithfield, Va.23430
  1838.                      Tel. (804)-357-0357 BBS
  1839.                      Tel. (804)-357-9190 FAX or Voice
  1840.                      
  1841.      Club-PC are also US distributors of my Assembly-Language Toolbox for
  1842.      QuickBASIC which contains a full set of window and display routines,
  1843.      as well as lots of other useful features.  Shareware versions are
  1844.      available both for QuickBASIC 4.5 and BASIC 7 and can be downloaded
  1845.      free from this board.
  1846.      
  1847.      **********************************************************************
  1848.      Christy Gemmell resides in England and was the major author of the
  1849.      Waite Group book QuickBASIC Bible. His new book is The Waite Group's
  1850.      QBASIC Bible. Christy also has a shareware called the Assembly
  1851.      Language Toolbox for QuickBASIC. Christy can be reached in care of 
  1852.      this newsletter.
  1853.      **********************************************************************
  1854.      
  1855.      
  1856.  
  1857.      The QBNews                                                     Page 30
  1858.      Volume  2, Number  4                                 December 22, 1991
  1859.  
  1860.  
  1861.  
  1862.      ----------------------------------------------------------------------
  1863.                         B e g i n n e r ' s   C o r n e r
  1864.      ----------------------------------------------------------------------
  1865.  
  1866.      Using COM 3 and COM 4 with QB by Dick Dennison
  1867.      
  1868.      Quick Basic is limited in its handling of COM ports to COM1 and COM2.
  1869.      This is becomming a recurring problem in these days of installed mouse
  1870.      systems as a mouse installed on COM1 also eliminates COM3 (likewise
  1871.      for COM2/COM4).  So what do you do if you have to run your modem on COM4
  1872.      and want to still use BASIC's OPEN COMx statements ?  Simple.  Stuff the
  1873.      address for COM4 where COM2 used to be.
  1874.      
  1875.      [Extracted from Pete Petrakis]
  1876.      
  1877.        Published memory maps for PCs consistently claim that BIOS memory
  1878.      addresses 0000:400-407 contain the addresses for serial port adapters 1
  1879.      through 4 (COM1 to COM4).  Thus, if you have four serial ports installed,
  1880.      you should be able to see the following if you type D 0000:400 in
  1881.      DEBUG:
  1882.      
  1883.               0000:0400  F8 03 F8 02 E8 03 E8 02 . . .
  1884.      
  1885.        You SHOULD be able to see that, but you probably won't!
  1886.      
  1887.        The chances are that you will see no more than F8 03 F8 02, which
  1888.      represent the port addresses for COM1 (3F8) and COM2 (2F8), and that
  1889.      the positions for COM3 and COM4 will contain only zeroes.  The problem
  1890.      is that hardware that creates COM3 and COM4 is likely NOT to poke the
  1891.      appropriate addresses for those ports into the BIOS communications data
  1892.      area.  
  1893.      
  1894.      [End of Extract]
  1895.      
  1896.      The addresses for the comports are:
  1897.             COM Port      Base Port Address 
  1898.      
  1899.              COM1             &H3F8         
  1900.              COM2             &H2F8         
  1901.              COM3             &H3E8         
  1902.              COM4             &H2E8         
  1903.      
  1904.      
  1905.      So (simply) what we want to do is:
  1906.      
  1907.              Step 1.  Save the current addresses in the BIOS communication
  1908.                       (&h400 - &h403) areas for later reset.
  1909.      
  1910.              Step 2.  Determine which port you need and stuff (poke) the 
  1911.                       appropriate address into COM1 (&h400-&h401) or 
  1912.                       COM2 (&h402-&h403).
  1913.      
  1914.              Step 3.  Use Basic's Open comx statements and do your thing.
  1915.      
  1916.      
  1917.      The QBNews                                                     Page 31
  1918.      Volume  2, Number  4                                 December 22, 1991
  1919.  
  1920.              Step 4.  Reinstall the old addresses.
  1921.      
  1922.      The accompanying source code expects the user to supply the comport number
  1923.      on the command line.
  1924.      
  1925.      [EDITOR'S NOTE]
  1926.      The source code for this article is in the file DIAL.ZIP.
  1927.      
  1928.  
  1929.  
  1930.  
  1931.  
  1932.  
  1933.  
  1934.  
  1935.  
  1936.  
  1937.  
  1938.  
  1939.  
  1940.  
  1941.  
  1942.  
  1943.  
  1944.  
  1945.  
  1946.  
  1947.  
  1948.  
  1949.  
  1950.  
  1951.  
  1952.  
  1953.  
  1954.  
  1955.  
  1956.  
  1957.  
  1958.  
  1959.  
  1960.  
  1961.  
  1962.  
  1963.  
  1964.  
  1965.  
  1966.  
  1967.  
  1968.  
  1969.  
  1970.  
  1971.  
  1972.  
  1973.  
  1974.      The QBNews                                                     Page 32
  1975.      Volume  2, Number  4                                 December 22, 1991
  1976.  
  1977.      The UEVENT Bug by Ray Crumrine
  1978.      
  1979.      While experimenting with a third party library product that uses
  1980.      SetUevent, I discovered the following bug in the QB (V4.5) UEVENT
  1981.      routines.  Microsoft assured me the bug has been fixed in BC7,
  1982.      although I have no way to test it.
  1983.      
  1984.      If you are trying to use ON TIMER and ON UEVENT in the same program,
  1985.      it probably won't work. If you type in the following code and run it
  1986.      you will find that once you press a key to CALL SetUevent, from that
  1987.      time on when the TIMER(5) seconds passes, QB jumps first to the
  1988.      section of code specified by ON TIMER(5) GOSUB, then IMMEDIATELY jumps
  1989.      to the section of code specified by ON UEVENT GOSUB! 
  1990.          
  1991.      The only way I could find to stop this was to use UEVENT OFF at the
  1992.      end of the user event section of code. If your user event is a one
  1993.      shot deal then you can work around this way. Otherwise, if your user
  1994.      event needs to stay enabled, you're probably out of luck. One other
  1995.      possible work around is for your user event to include code to
  1996.      determine if it is being called uselessly, then simply RETURN
  1997.      immediately. Besides being a kluge, this still wastes time jumping to
  1998.      the uevent code every time the TIMER goes off. A better work around is
  1999.      to NOT use ON TIMER and simply keep track of ElapsedTime using your
  2000.      own routines. This is fairly simple to do, and I have some code if
  2001.      anyone would like to look at it. 
  2002.      
  2003.      I check into this BBS once in a while, or you can write me at:  
  2004.      
  2005.        Ray Crumrine
  2006.        1800 Hilltop
  2007.        Quincy, Il. 62301                              
  2008.      
  2009.      Note that you must load the QB.QLB quick library to run this code.
  2010.      
  2011.      DECLARE SUB SetUevent ()              'In QB.QLB
  2012.      ON TIMER(5) GOSUB TimerEvent
  2013.      ON UEVENT GOSUB UserEvent
  2014.      CLS
  2015.      PRINT "Press any key to trigger the user event"
  2016.      TIMER ON
  2017.      UEVENT ON
  2018.      DO
  2019.        k$ = INKEY$
  2020.        IF LEN(k$) THEN
  2021.          IF k$ <> CHR$(27) THEN
  2022.            CALL SetUevent
  2023.          END IF
  2024.        END IF
  2025.      LOOP UNTIL k$ = CHR$(27)                
  2026.      END
  2027.      
  2028.      TimerEvent:
  2029.        PRINT "In the timer event section"
  2030.        RETURN
  2031.      
  2032.      The QBNews                                                     Page 33
  2033.      Volume  2, Number  4                                 December 22, 1991
  2034.  
  2035.      
  2036.      UserEvent:
  2037.        PRINT "In the user event section"
  2038.        'UEVENT OFF here will break the chain
  2039.        RETURN
  2040.      
  2041.  
  2042.  
  2043.  
  2044.  
  2045.  
  2046.  
  2047.  
  2048.  
  2049.  
  2050.  
  2051.  
  2052.  
  2053.  
  2054.  
  2055.  
  2056.  
  2057.  
  2058.  
  2059.  
  2060.  
  2061.  
  2062.  
  2063.  
  2064.  
  2065.  
  2066.  
  2067.  
  2068.  
  2069.  
  2070.  
  2071.  
  2072.  
  2073.  
  2074.  
  2075.  
  2076.  
  2077.  
  2078.  
  2079.  
  2080.  
  2081.  
  2082.  
  2083.  
  2084.  
  2085.  
  2086.  
  2087.  
  2088.  
  2089.      The QBNews                                                     Page 34
  2090.      Volume  2, Number  4                                 December 22, 1991
  2091.  
  2092.  
  2093.  
  2094.      ----------------------------------------------------------------------
  2095.                             F u n   a n d   G a m e s
  2096.      ----------------------------------------------------------------------
  2097.  
  2098.      Having a Ball by Charles Graham and David Cleary
  2099.      
  2100.      Those of you who frequent the QuickBASIC echo on FIDONet should know
  2101.      Charles Graham by now. Every so often he comes up with these little
  2102.      entertaining programming problems. He sent one to me that I have
  2103.      decided to include in The QBNews. I would like to have you send me
  2104.      your solutions to this problem. As a little incentive, the first
  2105.      person to correctly solves this problem will receive a free one year
  2106.      disk subscription to The QBNews. You will also have your name
  2107.      immortalized forever in the annals of QBNews history. So here it is and
  2108.      Good Luck!
  2109.      
  2110.      You have a balance scale and 12 balls that look and feel identical.
  2111.      Eleven of the balls are the same weight. One of the balls is slightly
  2112.      heavier or lighter -- you don't know which -- than the other 11. Using
  2113.      only the balance scale and only for 3 weighings, you must determine
  2114.      the odd ball, and whether it is lighter or heavier.
  2115.      
  2116.      To help you get started, here is the code to set up your 12 balls:
  2117.      
  2118.      '--------------------------------------------------------------------
  2119.      '
  2120.      DIM ball(12), attribute$(12)      'Set up 12 balls and attributes
  2121.      RANDOMIZE TIMER                   'Seed the RND function
  2122.                                        '
  2123.      begin:                            '
  2124.      FOR x = 1 TO 12                   'Give each ball an initial
  2125.          ball(x) = 1                   ' weight of 1 and an initial
  2126.          attribute$(x) = " equal"      ' attribute of "equal"
  2127.      NEXT x                            '
  2128.      oddball = INT(RND * 12) + 1       'Pick the odd ball randomly
  2129.      IF RND < .5 THEN                  'Pick the odd ball's weight:
  2130.          oddweight = .9                ' a little less
  2131.      ELSE                              ' or
  2132.          oddweight = 1.1               ' a little more
  2133.      END IF                            ' than the others
  2134.      ball(oddball) = oddweight         'Give the odd ball its odd weight
  2135.      
  2136.      That should be enough to get you started. The winner will be announced
  2137.      in the next issue of The QBNews. Get your entries in soon!
  2138.      
  2139.  
  2140.  
  2141.  
  2142.  
  2143.  
  2144.  
  2145.  
  2146.  
  2147.  
  2148.      The QBNews                                                     Page 35
  2149.  
  2150.